﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using RouterControl.IO;
using RouterControl.LocalDb;
using System.Reflection;
using RouterControl.Whs;

namespace RouterControl.Service
{
	public sealed class RouterMaster
	{
		private static RouterMaster _instance = new RouterMaster();

		public static RouterMaster Instance
		{
			get { return _instance; }
		}

		private readonly log4net.ILog _log = log4net.LogManager.GetLogger(typeof(RouterMaster));
		private Timer _gatherTimer;
		private object _gatherLock = new object();
		private volatile CommonInternetGateway _router;
        private List<RouterSlave> _slaves = new List<RouterSlave>();
        private Whs.WhsNotification _deviceLostNotification;
        private Whs.WhsNotification _deviceRestored;
        private bool _isRouterLost;
        private bool _firstRun;

        public bool IsRouterLost
        {
            get { return _isRouterLost; }
        }

        public T GetSlave<T>() where T : RouterSlave
        {
            foreach (var s in _slaves)
            {
                if (typeof(T).IsInstanceOfType(s))
                    return (T)s;
            }

            return null;
        }

		private void OnTick(object state)
		{
			try
			{
                // router is lost?
                if ((_firstRun || _isRouterLost) &&
                    (RouterDeviceFinder.Instance.SelectedRouter == null))
                {
                    _firstRun = false;

                    // search for router
                    if (!RouterDeviceFinder.Instance.Searching)
                    {
                        RouterDeviceFinder.Instance.StartSearch();
                    }

                    return;
                }

                // proceed normally
				UpdateData();
			}
			catch (DeviceConnectionLostException x)
			{
				_log.Warn("Device connection lost; stop gathering data", x);

				_router = null;

                OnDeviceChanged();
			}
			catch (Exception x)
			{
				_log.Warn("Failed to gather data", x);
			}
		}

		private void UpdateData()
		{
			// perform check
			lock (_gatherLock)
			{
				// get router state
				if ((_router == null) || (_router.RootDeviceId != Properties.Settings.Default.SelectedRouterDeviceUniqueName))
				{
					var dev = RouterDeviceFinder.Instance.SelectedRouter;

					if (dev != null)
					{
						_router = new CommonInternetGateway(dev);

                        OnDeviceChanged();
					}
				}

				if (_router == null)
					return;

                // gather data
                OnGatherData();
			}
		}

        private void OnGatherData()
        {
            _router.ClearCacheAll();

            // the slaves shall now gather the data
            foreach (var s in _slaves)
            {
                try
                {
                    s.OnDataUpdated();
                }
                catch (Exception ex)
                {
                    _log.Error("Failed to gather data for " + s.GetType().FullName, ex);
                }
            }
        }

        private void OnDeviceChanged()
        {
            // new device?
            if (_router != null)
            {
                // clear notification
                _deviceLostNotification.HideNotification();

                // prepare device lost notification
                // data might not be available if device lost
                _deviceLostNotification.Title = string.Format(
                    Properties.Resources.txtDeviceLostNotificationTitle,
                    _router.Device.FriendlyName,
                    _router.Device.UniqueDeviceName,
                    _router.Device.Type
                    );

                _deviceLostNotification.Description = string.Format(
                    Properties.Resources.txtDeviceLostNotificationDesc,
                    _router.Device.FriendlyName,
                    _router.Device.UniqueDeviceName,
                    _router.Device.Type
                    );

                // notify the user of the restore
                if (_isRouterLost)
                {
                    var timeout = Properties.Settings.Default.DefaultUserNotificationShowTime;

                    _deviceRestored.Title = string.Format(
                        Properties.Resources.txtDeviceRestoredNotificationTitle,
                        _router.Device.FriendlyName,
                        _router.Device.UniqueDeviceName,
                        _router.Device.Type,
                        timeout.TotalSeconds
                        );

                    _deviceRestored.Description = string.Format(
                        Properties.Resources.txtDeviceRestoredNotificationDesc,
                        _router.Device.FriendlyName,
                        _router.Device.UniqueDeviceName,
                        _router.Device.Type,
                        timeout.TotalSeconds
                        );

                    _deviceRestored.ShowNotifcation(timeout);
                }

                // register for device lost
                _isRouterLost = false;

                _router.Device.OnConnectionLost += new RootDevice.OnConnectionLostDelegate(OnConnectionLost);
            }

            // inform the devices
            foreach (var s in _slaves)
            {
                try
                {
                    s.OnRouterChanged(_router);
                }
                catch (Exception ex)
                {
                    _log.Error("Failed to change device for " + s.GetType().FullName, ex);
                }
            }
        }

        private void OnConnectionLost(object sender, EventArgs e)
        {
            _log.Warn("Device connection lost; stop gathering data");


            // handle loss
            _isRouterLost = true;
            _router = null;

            OnDeviceChanged();

            // notify user
            // notification is already prepare, because
            // data might not be available if device lost
            _deviceLostNotification.ShowNotifcation();
        }

        private void InstanceSlavesFromAssembly(Assembly asm)
        {
            if (asm == null)
                throw new ArgumentNullException("asm");

            if (_log.IsDebugEnabled)
                _log.DebugFormat("Gathering slaves from assembly '{0}'...", asm.FullName);

            // gather services
            int counter = 0;

            foreach (var t in asm.GetTypes())
            {
                foreach (RouterSlaveAttribute a in t.GetCustomAttributes(typeof(RouterSlaveAttribute), false))
                {
                    counter++;

                    if (_log.IsDebugEnabled)
                        _log.DebugFormat("  " + t.FullName);

                    // create
                    try
                    {
                        _slaves.Add((RouterSlave)t.InvokeMember(
                            string.Empty,
                            BindingFlags.CreateInstance,
                            null,
                            null,
                            new object[0]
                            ));
                    }
                    catch (Exception ex)
                    {
                        _log.Error("Failed to create device slave: " + t.FullName, ex);
                    }
                }
            }

            _log.InfoFormat("Slaves found in assembly '{0}': {1}", asm.FullName, counter);
        }

        private RouterMaster()
		{
			if (_log.IsDebugEnabled)
				_log.Debug("Creating singleton");

			try
			{
                // instance slaves
                InstanceSlavesFromAssembly(Assembly.GetExecutingAssembly());

                // create notifications
                _deviceLostNotification = new Whs.WhsNotification(Whs.Properties.Constants.DeviceLostNotificationId)
                {
                    Severity = Microsoft.HomeServer.SDK.Interop.v1.WHS_Notification_Severity.WHS_ERROR
                };

                _deviceRestored = new Whs.WhsNotification(Whs.Properties.Constants.DeviceRestoredNotificationId)
                {
                    Severity = Microsoft.HomeServer.SDK.Interop.v1.WHS_Notification_Severity.WHS_WARNING
                };

                // create timer
				_gatherTimer = new Timer(
					new TimerCallback(OnTick),
					null,
					1000,
					Properties.Settings.Default.RouterMasterUpdateInterval
					);
			}
			catch (Exception e)
			{
				_log.Fatal("Failed to create singleton", e);

				throw;
			}

		}
	}
}
